/*
 * showevtinfo.c - show event information
 *
 * Copyright (c) 2010 Google, Inc
 * Contributed by Stephane Eranian <eranian@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * This file is part of libpfm, a performance monitoring support library for
 * applications on Linux.
 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <regex.h>

#include <perfmon/pfmlib.h>

#define MAXBUF		1024
#define COMBO_MAX	18

static struct {
  int compact;
  int sort;
  int encode;
  int combo;
  int combo_lim;
  int desc;
  char *csv_sep;
  pfm_event_info_t efilter;
  pfm_event_attr_info_t ufilter;
  pfm_os_t os;
  uint64_t mask;
} options;

typedef struct {
  uint64_t code;
  int idx;
} code_info_t;

static void show_event_info_compact(pfm_event_info_t *info);

static const char *srcs[PFM_ATTR_CTRL_MAX]={
  [PFM_ATTR_CTRL_UNKNOWN] = "???",
  [PFM_ATTR_CTRL_PMU] = "PMU",
  [PFM_ATTR_CTRL_PERF_EVENT] = "perf_event",
};

#ifdef PFMLIB_WINDOWS
int set_env_var(const char *var, const char *value, int ov)
{
  size_t len;
  char *str;
  int ret;

  len = strlen(var) + 1 + strlen(value) + 1;

  str = malloc(len);
  if (!str)
    return PFM_ERR_NOMEM;

  sprintf(str, "%s=%s", var, value);

  ret = putenv(str);

  free(str);

  return ret ? PFM_ERR_INVAL : PFM_SUCCESS;
}
#else
static inline int
set_env_var(const char *var, const char *value, int ov)
{
  return setenv(var, value, ov);
}
#endif

static int
event_has_pname(char *s)
{
  char *p;
  return (p = strchr(s, ':')) && *(p+1) == ':';
}

static int
print_codes(char *buf, int plm, int max_encoding)
{
  uint64_t *codes = NULL;
  int j, ret, count = 0;

  ret = pfm_get_event_encoding(buf, PFM_PLM0|PFM_PLM3, NULL, NULL, &codes, &count);
  if (ret != PFM_SUCCESS) {
    if (ret == PFM_ERR_NOTFOUND)
      fprintf(stderr, "encoding failed, try setting env variable LIBPFM_ENCODE_INACTIVE=1");
    return -1;
  }
  for(j = 0; j < max_encoding; j++) {
    if (j < count)
      printf("0x%"PRIx64, codes[j]);
    printf("%s", options.csv_sep);
  }
  free(codes);
  return 0;
}

static int
check_valid(char *buf, int plm)
{
  uint64_t *codes = NULL;
  int ret, count = 0;

  ret = pfm_get_event_encoding(buf, PFM_PLM0|PFM_PLM3, NULL, NULL, &codes, &count);
  if (ret != PFM_SUCCESS)
    return -1;
  free(codes);
  return 0;
}

static int
match_ufilters(pfm_event_attr_info_t *info)
{
  uint32_t ufilter1 = 0;
  uint32_t ufilter2 = 0;

  if (options.ufilter.is_dfl)
    ufilter1 |= 0x1;

  if (info->is_dfl)
    ufilter2 |= 0x1;

  if (options.ufilter.is_precise)
    ufilter1 |= 0x2;

  if (info->is_precise)
    ufilter2 |= 0x2;

  if (!ufilter1)
    return 1;

  /* at least one filter matches */
  return ufilter1 & ufilter2;
}

static int
match_efilters(pfm_event_info_t *info)
{
  pfm_event_attr_info_t ainfo;
  int n = 0;
  int i, ret;

  if (options.efilter.is_precise && !info->is_precise)
    return 0;

  memset(&ainfo, 0, sizeof(ainfo));
  ainfo.size = sizeof(ainfo);

  pfm_for_each_event_attr(i, info) {
    ret = pfm_get_event_attr_info(info->idx, i, options.os, &ainfo);
    if (ret != PFM_SUCCESS)
      continue;
    if (match_ufilters(&ainfo))
      return 1;
    if (ainfo.type == PFM_ATTR_UMASK)
      n++;
  }
  return n ? 0 : 1;
}

static void
show_event_info_combo(pfm_event_info_t *info)
{
  pfm_event_attr_info_t *ainfo;
  pfm_pmu_info_t pinfo;
  char buf[MAXBUF];
  size_t len;
  int numasks = 0;
  int i, j, ret;
  uint64_t total, m, u;

  memset(&pinfo, 0, sizeof(pinfo));

  pinfo.size = sizeof(pinfo);

  ret = pfm_get_pmu_info(info->pmu, &pinfo);
  if (ret != PFM_SUCCESS) {
    fprintf(stderr, "cannot get PMU info");
    abort();
  }

  ainfo = calloc(info->nattrs, sizeof(*ainfo));
  if (!ainfo) {
    fprintf(stderr, "event %s : ", info->name);
    abort();
  }

  /*
   * extract attribute information and count number
   * of umasks
   *
   * we cannot just drop non umasks because we need
   * to keep attributes in order for the enumeration
   * of 2^n
   */
  pfm_for_each_event_attr(i, info) {
    ainfo[i].size = sizeof(*ainfo);

    ret = pfm_get_event_attr_info(info->idx, i, options.os, &ainfo[i]);
    if (ret != PFM_SUCCESS) {
      fprintf(stderr, "cannot get attribute info: %s", pfm_strerror(ret));
      abort();
    }

    if (ainfo[i].type == PFM_ATTR_UMASK)
      numasks++;
  }
  if (numasks > options.combo_lim) {
    fprintf(stderr, "event %s has too many umasks to print all combinations, dropping to simple enumeration", info->name);
    free(ainfo);
    show_event_info_compact(info);
    return;
  }

  if (numasks) {
    if (info->nattrs > (int)((sizeof(total)<<3))) {
      fprintf(stderr, "too many umasks, cannot show all combinations for event %s", info->name);
      goto end;
    }
    total = 1ULL << info->nattrs;

    for (u = 1; u < total; u++) {
      len = sizeof(buf);
      len -= snprintf(buf, len, "%s::%s", pinfo.name, info->name);
      if (len <= 0) {
	fprintf(stderr, "event name too long%s", info->name);
	goto end;
      }
      for(m = u, j = 0; m; m >>=1, j++) {
	if (m & 0x1ULL) {
	  /* we have hit a non umasks attribute, skip */
	  if (ainfo[j].type != PFM_ATTR_UMASK)
	    break;

	  if (len < (1 + strlen(ainfo[j].name))) {
	    fprintf(stderr, "umasks combination too long for event %s", buf);
	    break;
	  }
	  strncat(buf, ":", len-1);buf[len-1] = '\0'; len--;
	  strncat(buf, ainfo[j].name, len-1);buf[len-1] = '\0';
	  len -= strlen(ainfo[j].name);
	}
      }
      /* if found a valid umask combination, check encoding */
      if (m == 0) {
	if (options.encode)
	  ret = print_codes(buf, PFM_PLM0|PFM_PLM3, pinfo.max_encoding);
	else
	  ret = check_valid(buf, PFM_PLM0|PFM_PLM3);
	if (!ret)
	  printf("%s\n", buf);
      }
    }
  } else {
    snprintf(buf, sizeof(buf)-1, "%s::%s", pinfo.name, info->name);
    buf[sizeof(buf)-1] = '\0';

    ret = options.encode ? print_codes(buf, PFM_PLM0|PFM_PLM3, pinfo.max_encoding) : 0;
    if (!ret)
      printf("%s\n", buf);
  }
 end:
  free(ainfo);
}

static void
show_event_info_compact(pfm_event_info_t *info)
{
  pfm_event_attr_info_t ainfo;
  pfm_pmu_info_t pinfo;
  char buf[MAXBUF];
  int i, ret, um = 0;

  memset(&ainfo, 0, sizeof(ainfo));
  memset(&pinfo, 0, sizeof(pinfo));

  pinfo.size = sizeof(pinfo);
  ainfo.size = sizeof(ainfo);

  ret = pfm_get_pmu_info(info->pmu, &pinfo);
  if (ret != PFM_SUCCESS) {
    fprintf(stderr, "cannot get pmu info: %s", pfm_strerror(ret));
    abort();
  }

  pfm_for_each_event_attr(i, info) {
    ret = pfm_get_event_attr_info(info->idx, i, options.os, &ainfo);
    if (ret != PFM_SUCCESS) {
      fprintf(stderr, "cannot get attribute info: %s", pfm_strerror(ret));
      abort();
    }

    if (ainfo.type != PFM_ATTR_UMASK)
      continue;

    if (!match_ufilters(&ainfo))
      continue;

    snprintf(buf, sizeof(buf)-1, "%s::%s:%s", pinfo.name, info->name, ainfo.name);
    buf[sizeof(buf)-1] = '\0';

    ret = 0;
    if (options.encode) {
      ret = print_codes(buf, PFM_PLM0|PFM_PLM3, pinfo.max_encoding);
    }
    if (!ret) {
      printf("%s", buf);
      if (options.desc) {
	printf("%s", options.csv_sep);
	printf("\"%s. %s.\"", info->desc, ainfo.desc);
      }
      putchar('\n');
    }
    um++;
  }
  if (um == 0) {
    if (!match_efilters(info))
      return;

    snprintf(buf, sizeof(buf)-1, "%s::%s", pinfo.name, info->name);
    buf[sizeof(buf)-1] = '\0';
    if (options.encode) {
      ret = print_codes(buf, PFM_PLM0|PFM_PLM3, pinfo.max_encoding);
      if (ret)
	return;
    }
    printf("%s", buf);
    if (options.desc) {
      printf("%s", options.csv_sep);
      printf("\"%s.\"", info->desc);
    }
    putchar('\n');
  }
}

int compare_codes(const void *a, const void *b)
{
  const code_info_t *aa = a;
  const code_info_t *bb = b;
  uint64_t m = options.mask;

  if ((aa->code & m) < (bb->code &m))
    return -1;
  if ((aa->code & m) == (bb->code & m))
    return 0;
  return 1;
}

static void
print_event_flags(pfm_event_info_t *info)
{
  int n = 0;

  if (info->is_precise) {
    printf("[precise] ");
    n++;
  }
  if (!n)
    printf("None");
}

static void
print_attr_flags(pfm_event_attr_info_t *info)
{
  int n = 0;

  if (info->is_dfl) {
    printf("[default] ");
    n++;
  }

  if (info->is_precise) {
    printf("[precise] ");
    n++;
  }

  if (!n)
    printf("None ");
}


static void
show_event_info(pfm_event_info_t *info)
{
  pfm_event_attr_info_t ainfo;
  pfm_pmu_info_t pinfo;
  int mod = 0, um = 0;
  int i, ret;
  const char *src;

  memset(&ainfo, 0, sizeof(ainfo));
  memset(&pinfo, 0, sizeof(pinfo));

  pinfo.size = sizeof(pinfo);
  ainfo.size = sizeof(ainfo);

  if (!match_efilters(info))
    return;
  ret = pfm_get_pmu_info(info->pmu, &pinfo);
  if (ret) {
    fprintf(stderr, "cannot get pmu info: %s", pfm_strerror(ret));
    abort();
  }

  printf("#-----------------------------\n"
	 "IDX	 : %d\n"
	 "PMU name : %s (%s)\n"
	 "Name     : %s\n"
	 "Equiv	 : %s\n",
	 info->idx,
	 pinfo.name,
	 pinfo.desc,
	 info->name,
	 info->equiv ? info->equiv : "None");

  printf("Flags    : ");
  print_event_flags(info);
  putchar('\n');

  printf("Desc     : %s\n", info->desc ? info->desc : "no description available");
  printf("Code     : 0x%"PRIx64"\n", info->code);

  pfm_for_each_event_attr(i, info) {
    ret = pfm_get_event_attr_info(info->idx, i, options.os, &ainfo);
    if (ret != PFM_SUCCESS) {
      fprintf(stderr, "cannot retrieve event %s attribute info: %s", info->name, pfm_strerror(ret));
      abort();
    }

    if (ainfo.ctrl >= PFM_ATTR_CTRL_MAX) {
      fprintf(stderr, "event: %s has unsupported attribute source %d", info->name, ainfo.ctrl);
      ainfo.ctrl = PFM_ATTR_CTRL_UNKNOWN;
    }
    src = srcs[ainfo.ctrl];
    switch(ainfo.type) {
    case PFM_ATTR_UMASK:
      if (!match_ufilters(&ainfo))
	continue;

      printf("Umask-%02u : 0x%02"PRIx64" : %s : [%s] : ",
	     um,
	     ainfo.code,
	     src,
	     ainfo.name);

      print_attr_flags(&ainfo);

      putchar(':');

      if (ainfo.equiv)
	printf(" Alias to %s", ainfo.equiv);
      else
	printf(" %s", ainfo.desc);

      putchar('\n');
      um++;
      break;
    case PFM_ATTR_MOD_BOOL:
      printf("Modif-%02u : 0x%02"PRIx64" : %s : [%s] : %s (boolean)\n", mod, ainfo.code, src, ainfo.name, ainfo.desc);
      mod++;
      break;
    case PFM_ATTR_MOD_INTEGER:
      printf("Modif-%02u : 0x%02"PRIx64" : %s : [%s] : %s (integer)\n", mod, ainfo.code, src, ainfo.name, ainfo.desc);
      mod++;
      break;
    default:
      printf("Attr-%02u  : 0x%02"PRIx64" : %s : [%s] : %s\n", i, ainfo.code, ainfo.name, src, ainfo.desc);
    }
  }
}


static int
show_info(char *event, regex_t *preg)
{
  pfm_pmu_info_t pinfo;
  pfm_event_info_t info;
  int i, j, ret, match = 0, pname;
  size_t len, l = 0;
  char *fullname = NULL;

  memset(&pinfo, 0, sizeof(pinfo));
  memset(&info, 0, sizeof(info));

  pinfo.size = sizeof(pinfo);
  info.size = sizeof(info);

  pname = event_has_pname(event);

  /*
   * scan all supported events, incl. those
   * from undetected PMU models
   */
  pfm_for_all_pmus(j) {

    ret = pfm_get_pmu_info(j, &pinfo);
    if (ret != PFM_SUCCESS)
      continue;

    /* no pmu prefix, just look for detected PMU models */
    if (!pname && !pinfo.is_present)
      continue;

    for (i = pinfo.first_event; i != -1; i = pfm_get_event_next(i)) {
      ret = pfm_get_event_info(i, options.os, &info);
      if (ret != PFM_SUCCESS) {
	fprintf(stderr, "cannot get event info: %s", pfm_strerror(ret));
	abort();
      }

      len = strlen(info.name) + strlen(pinfo.name) + 1 + 2;
      if (len > l) {
	l = len;
	fullname = realloc(fullname, l);
	if (!fullname) {
	  fprintf(stderr, "cannot allocate memory");
	  abort();
	}
      }
      sprintf(fullname, "%s::%s", pinfo.name, info.name);

      if (regexec(preg, fullname, 0, NULL, 0) == 0) {
	if (options.compact)
	  if (options.combo)
	    show_event_info_combo(&info);
	  else
	    show_event_info_compact(&info);
	else
	  show_event_info(&info);
	match++;
      }
    }
  }
  if (fullname)
    free(fullname);

  return match;
}

static int
show_info_sorted(char *event, regex_t *preg)
{
  pfm_pmu_info_t pinfo;
  pfm_event_info_t info;
  unsigned int j;
  int i, ret, n, match = 0;
  size_t len, l = 0;
  char *fullname = NULL;
  code_info_t *codes;

  memset(&pinfo, 0, sizeof(pinfo));
  memset(&info, 0, sizeof(info));

  pinfo.size = sizeof(pinfo);
  info.size = sizeof(info);

  pfm_for_all_pmus(j) {

    ret = pfm_get_pmu_info(j, &pinfo);
    if (ret != PFM_SUCCESS)
      continue;

    codes = malloc(pinfo.nevents * sizeof(*codes));
    if (!codes) {
      fprintf(stderr, "cannot allocate memory\n");
      abort();
    }

    /* scans all supported events */
    n = 0;
    for (i = pinfo.first_event; i != -1; i = pfm_get_event_next(i)) {

      ret = pfm_get_event_info(i, options.os, &info);
      if (ret != PFM_SUCCESS) {
	fprintf(stderr, "cannot gt event info: %s", pfm_strerror(ret));
	abort();
      }

      if (info.pmu != j)
	continue;

      codes[n].idx = info.idx;
      codes[n].code = info.code;
      n++;
    }
    qsort(codes, n, sizeof(*codes), compare_codes);
    for(i=0; i < n; i++) {
      ret = pfm_get_event_info(codes[i].idx, options.os, &info);
      if (ret != PFM_SUCCESS) {
	fprintf(stderr, "cannot get event info: %s", pfm_strerror(ret));
	abort();
      }

      len = strlen(info.name) + strlen(pinfo.name) + 1 + 2;
      if (len > l) {
	l = len;
	fullname = realloc(fullname, l);
	if (!fullname) {
	  fprintf(stderr, "cannot allocate memory");
	  abort();
	}
      }
      sprintf(fullname, "%s::%s", pinfo.name, info.name);

      if (regexec(preg, fullname, 0, NULL, 0) == 0) {
	if (options.compact)
	  show_event_info_compact(&info);
	else
	  show_event_info(&info);
	match++;
      }
    }
    free(codes);
  }
  if (fullname)
    free(fullname);

  return match;
}

static void
usage(void)
{
  printf("showevtinfo [-L] [-E] [-h] [-s] [-m mask]\n"
	 "-L\t\tlist one event per line (compact mode)\n"
	 "-E\t\tlist one event per line with encoding (compact mode)\n"
	 "-M\t\tdisplay all valid unit masks combination (use with -L or -E)\n"
	 "-h\t\tget help\n"
	 "-s\t\tsort event by PMU and by code based on -m mask\n"
	 "-l\t\tmaximum number of umasks to list all combinations (default: %d)\n"
	 "-F\t\tshow only events and attributes with certain flags (precise,...)\n"
	 "-m mask\t\thexadecimal event code mask, bits to match when sorting\n"
	 "-x sep\t\tuse sep as field separator in compact mode\n"
	 "-D\t\t\tprint event description in compact mode\n"
	 "-O os\t\tshow attributes for the specific operating system\n",
	 COMBO_MAX);
}

/*
 * keep: [pmu::]event
 * drop everything else
 */
static void
drop_event_attributes(char *str)
{
  char *p;

  p = strchr(str, ':');
  if (!p)
    return;

  str = p+1;
  /* keep PMU name */
  if (*str == ':')
    str++;

  /* stop string at 1st attribute */
  p = strchr(str, ':');
  if (p)
    *p = '\0';
}

#define EVENT_FLAGS(n, f, l) { .name = n, .ebit = f, .ubit = l }
struct attr_flags {
  const char *name;
  int ebit; /* bit position in pfm_event_info_t.flags, -1 means ignore */
  int ubit; /*  bit position in pfm_event_attr_info_t.flags, -1 means ignore */
};

static const struct attr_flags  event_flags[]={
  EVENT_FLAGS("precise", 0, 1),
  EVENT_FLAGS("pebs", 0, 1),
  EVENT_FLAGS("default", -1, 0),
  EVENT_FLAGS("dfl", -1, 0),
  EVENT_FLAGS(NULL, 0, 0)
};

static void
parse_filters(char *arg)
{
  const struct attr_flags *attr;
  char *p;

  while (arg) {
    p = strchr(arg, ',');
    if (p)
      *p++ = 0;

    for (attr = event_flags; attr->name; attr++) {
      if (!strcasecmp(attr->name, arg)) {
	switch(attr->ebit) {
	case 0:
	  options.efilter.is_precise = 1;
	  break;
	case -1:
	  break;
	default:
	  fprintf(stderr, "unknown event flag %d", attr->ebit);
	  abort();
	}
	switch (attr->ubit) {
	case 0:
	  options.ufilter.is_dfl = 1;
	  break;
	case 1:
	  options.ufilter.is_precise = 1;
	  break;
	case -1:
	  break;
	default:
	  fprintf(stderr, "unknown umaks flag %d", attr->ubit);
	  abort();
	}
	break;
      }
    }
    arg = p;
  }
}

static const struct {
  char *name;
  pfm_os_t os;
} supported_oses[]={
  { .name = "none", .os = PFM_OS_NONE },
  { .name = "raw", .os = PFM_OS_NONE },
  { .name = "pmu", .os = PFM_OS_NONE },

  { .name = "perf", .os = PFM_OS_PERF_EVENT},
  { .name = "perf_ext", .os = PFM_OS_PERF_EVENT_EXT},
  { .name = NULL, }
};

static const char *pmu_types[]={
  "unknown type",
  "core",
  "uncore",
  "OS generic",
};

static void
setup_os(char *ostr)
{
  int i;

  for (i = 0; supported_oses[i].name; i++) {
    if (!strcmp(supported_oses[i].name, ostr)) {
      options.os = supported_oses[i].os;
      return;
    }
  }
  fprintf(stderr, "unknown OS layer %s, choose from:", ostr);
  for (i = 0; supported_oses[i].name; i++) {
    if (i)
      fputc(',', stderr);
    fprintf(stderr, " %s", supported_oses[i].name);
  }
  fputc('\n', stderr);
  exit(1);
}

int
main(int argc, char **argv)
{
  static char *argv_all[2] = { ".*", NULL };
  pfm_pmu_info_t pinfo;
  char *endptr = NULL;
  char default_sep[2] = "\t";
  char *ostr = NULL;
  char **args;
  int i, match;
  regex_t preg;
  int ret, c;

  memset(&pinfo, 0, sizeof(pinfo));

  pinfo.size = sizeof(pinfo);

  while ((c=getopt(argc, argv,"hELsm:Ml:F:x:DO:")) != -1) {
    switch(c) {
    case 'L':
      options.compact = 1;
      break;
    case 'F':
      parse_filters(optarg);
      break;
    case 'E':
      options.compact = 1;
      options.encode = 1;
      break;
    case 'M':
      options.combo = 1;
      break;
    case 's':
      options.sort = 1;
      break;
    case 'D':
      options.desc = 1;
      break;
    case 'l':
      options.combo_lim = atoi(optarg);
      break;
    case 'x':
      options.csv_sep = optarg;
      break;
    case 'O':
      ostr = optarg;
      break;
    case 'm':
      options.mask = strtoull(optarg, &endptr, 16);
      if (*endptr) {
	fprintf(stderr, "mask must be in hexadecimal\n");
	abort();
      }
      break;
    case 'h':
      usage();
      exit(0);
    default:
      fprintf(stderr, "unknown option error");
      abort();
    }
  }
  /* to allow encoding of events from non detected PMU models */
  ret = set_env_var("LIBPFM_ENCODE_INACTIVE", "1", 1);
  if (ret != PFM_SUCCESS) {
    fprintf(stderr, "cannot force inactive encoding");
    abort();
  }


  ret = pfm_initialize();
  if (ret != PFM_SUCCESS) {
    fprintf(stderr, "cannot initialize libpfm: %s", pfm_strerror(ret));
    abort();
  }

  if (options.mask == 0)
    options.mask = ~0;

  if (optind == argc) {
    args = argv_all;
  } else {
    args = argv + optind;
  }
  if (!options.csv_sep)
    options.csv_sep = default_sep;

  /* avoid combinatorial explosion */
  if (options.combo_lim == 0)
    options.combo_lim = COMBO_MAX;

  if (ostr)
    setup_os(ostr);
  else
    options.os = PFM_OS_NONE;

  if (!options.compact) {
    int total_supported_events = 0;
    int total_available_events = 0;

    printf("Supported PMU models:\n");
    pfm_for_all_pmus(i) {
      ret = pfm_get_pmu_info(i, &pinfo);
      if (ret != PFM_SUCCESS)
	continue;

      printf("\t[%d, %s, \"%s\"]\n", i, pinfo.name,  pinfo.desc);
    }

    printf("Detected PMU models:\n");
    pfm_for_all_pmus(i) {
      ret = pfm_get_pmu_info(i, &pinfo);
      if (ret != PFM_SUCCESS)
	continue;

      if (pinfo.is_present) {
	if (pinfo.type >= PFM_PMU_TYPE_MAX)
	  pinfo.type = PFM_PMU_TYPE_UNKNOWN;

	printf("\t[%d, %s, \"%s\", %d events, %d max encoding, %d counters, %s PMU]\n",
	       i,
	       pinfo.name,
	       pinfo.desc,
	       pinfo.nevents,
	       pinfo.max_encoding,
	       pinfo.num_cntrs + pinfo.num_fixed_cntrs,
	       pmu_types[pinfo.type]);

	total_supported_events += pinfo.nevents;
      }
      total_available_events += pinfo.nevents;
    }
    printf("Total events: %d available, %d supported\n", total_available_events, total_supported_events);
  }

  while(*args) {
    /* drop umasks and modifiers */
    drop_event_attributes(*args);
    if (regcomp(&preg, *args, REG_ICASE)) {
      fprintf(stderr, "error in regular expression for event \"%s\"", *argv);
      abort();
    }

    if (options.sort)
      match = show_info_sorted(*args, &preg);
    else
      match = show_info(*args, &preg);

    if (match == 0) {
      fprintf(stderr, "event %s not found", *args);
      abort();
    }

    args++;
  }

  regfree(&preg);

  pfm_terminate();

  return 0;
}
